/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include <chrono>
#include "ObjectStorageBackupJob.h"
#include "parser/FileNameListParser.h"
#include "parser/BucketLogParser.h"

using namespace OBSPlugin;

namespace {
    const std::string MODULE = "ObjectStorageBackupJobBucketLogInc";
    const std::string TRUE_STR = "true";
    const std::string LOG_FILE_NAME_LIST_FILE_NAME = "logFileNameList";
    const std::string OBJECT_LIST_FILE_ORIGINAL_DIR_NAME = "original";
    const std::string OBJECT_LIST_FILE_UNIQUE_DIR_NAME = "unique";
}

bool ObjectStorageBackupJob::IsBucketLogInc() const
{
    if (IsFullBackup()) {
        HCP_Log(INFO, MODULE) << "Full backup does not need download log, jobId: " << m_jobId << HCPENDLOG;
        return false;
    }

    if (m_protectedOBS.advParms.m_useBucketLog != TRUE_STR) {
        HCP_Log(INFO, MODULE) << "Bucket log switch is off, jobId: " << m_jobId << HCPENDLOG;
        return false;
    }

    return true;
}

std::string ObjectStorageBackupJob::GetCurrentTimeStamp()
{
    auto now = std::chrono::system_clock::now();
    std::time_t now_c = std::chrono::system_clock::to_time_t(now);
    std::tm utc_tm;
    gmtime_r(&now_c, &utc_tm);
    std::ostringstream oss;
    oss << std::put_time(&utc_tm, "%Y-%m-%d-%H-%M-%S");
    std::string str = oss.str();
    HCP_Log(DEBUG, MODULE) <<"current utc timestamp:"<< str << HCPENDLOG;
    return str;
}
bool ObjectStorageBackupJob::RetrieveFullLogStamp()
{
    std::string  currentTimeStamp = GetCurrentTimeStamp();
    for (const auto& object : m_protectedOBS.obsProtectSubObjectList) {
        std::string bucketName = object.bucketName;
        HCP_Log(DEBUG, MODULE) << "bucketName " << bucketName << " currentTimeStamp " << currentTimeStamp <<HCPENDLOG;
        if (!SetBucketLogTimestamp(bucketName, currentTimeStamp)) {
            HCP_Log(ERR, MODULE) << "Setting the timestamp for the bucket failed" << bucketName << HCPENDLOG;
            return false;
        }
    }

    return true;
}

bool ObjectStorageBackupJob::GetBucketLogs()
{
    if (!IsBucketLogInc()) {
        return FullBackupUpdateGeneralInfo();
    }

    ReportJobDetailsWithLabelAndErrcode(
        std::make_tuple(JobLogLevel::TASK_LOG_INFO, SubJobStatus::RUNNING, OBSPlugin::PROGRESS0),
        "object_storage_plugin_bucket_log_inc_backup_start_label",
        0);

    Copy lastCopy;
    if (!QueryPreviousCopy(lastCopy)) {
        HCP_Log(ERR, MODULE) << "Get last copy failed, jobid: " << m_jobId << HCPENDLOG;
        return false;
    }

    if (!Module::JsonHelper::JsonStringToStruct(
        lastCopy.extendInfo, m_backupObjectCopyInfo)) {
        HCP_Log(ERR, MODULE) << "Convert last copy failed, jobid: " << m_jobId << HCPENDLOG;
        return false;
    }

    if (!InitCloudClient()) {
        HCP_Log(ERR, MODULE) << "InitCloudClient failed, jobId: " << m_jobId << HCPENDLOG;
        return false;
    }

    for (const auto& object : m_protectedOBS.obsProtectSubObjectList) {
        const std::string bucketLogDownloadDir = PathJoin(m_bucketLogDir, object.bucketName);
        if (!PrepareBucketLog(object.bucketName, bucketLogDownloadDir)) {
            HCP_Log(ERR, MODULE) << "Prepare bucket log failed for " << object.bucketName
                << ", jobId: " << m_jobId << HCPENDLOG;
            return false;
        }
    }

    for (const auto& object : m_protectedOBS.obsProtectSubObjectList) {
        if (!ParseBucketLog(object)) {
            HCP_Log(ERR, MODULE) << "Parse bucket log failed!" << HCPENDLOG;
            ReportJobDetailsWithLabelAndErrcode(
                std::make_tuple(JobLogLevel::TASK_LOG_ERROR, SubJobStatus::FAILED, OBSPlugin::PROGRESS0),
                "object_storage_plugin_parse_bucket_log_failed_label",
                ObsErrorCode::ERROR_AGENT_INTERNAL_ERROR,
                object.bucketName);
            return false;
        }
    }

    if (!UpdateGeneralResource(m_jobId, m_generalInfo)) {
        HCP_Log(ERR, MODULE) << "UpdateGeneralResource failed" << HCPENDLOG;
        return false;
    }

    return true;
}

bool ObjectStorageBackupJob::FullBackupUpdateGeneralInfo()
{
    HCP_Log(INFO, MODULE) << "Skip bucket log inc, jobId: " << m_jobId << HCPENDLOG;
    RetrieveFullLogStamp();
    if (!UpdateGeneralResource(m_jobId, m_generalInfo)) {
        HCP_Log(ERR, MODULE) << "UpdateGeneralResource failed" << HCPENDLOG;
        return false;
    }
    return true;
}

bool ObjectStorageBackupJob::PrepareBucketLog(const std::string& bucketName, const std::string& bucketLogDownloadDir)
{
    auto getBucketLogConfigreRquest = std::make_unique<Module::GetBucketLogConfigRequest>();
    getBucketLogConfigreRquest->bucketName = bucketName;
    std::unique_ptr<Module::GetBucketLogConfigResponse> getBucketLogConfigResponse;
    auto result = m_cloudClient->GetBucketLogConfig(getBucketLogConfigreRquest, getBucketLogConfigResponse);
    if (!result.IsSucc()) {
        HCP_Log(ERR, MODULE) << "GetBucketLogConfig failed, bucket name " << bucketName << HCPENDLOG;
        ReportJobDetailsWithLabelAndErrcode(
            std::make_tuple(JobLogLevel::TASK_LOG_ERROR, SubJobStatus::FAILED, OBSPlugin::PROGRESS0),
            "object_storage_plugin_get_bucket_log_config_failed_label", result.GetCommonErrorCode());
        return false;
    }

    if (getBucketLogConfigResponse->targetBucket.empty()) {
        HCP_Log(ERR, MODULE) << "Bucket has no configuration log, bucket name " << bucketName << HCPENDLOG;
        ReportJobDetailsWithLabelAndErrcode(
            std::make_tuple(JobLogLevel::TASK_LOG_ERROR, SubJobStatus::FAILED, OBSPlugin::PROGRESS0),
            "object_storage_plugin_get_bucket_log_config_failed_label", 0);
        return false;
    }

    if (!OBSPlugin::Remove(bucketLogDownloadDir)) {
        HCP_Log(WARN, MODULE) << "Clear bucket log dir failed, dir " << bucketLogDownloadDir << HCPENDLOG;
    }
    if (!OBSPlugin::RecurseCreateDirectory(bucketLogDownloadDir)) {
        HCP_Log(ERR, MODULE) << "Create bucket log download dir failed, dir " << bucketLogDownloadDir << HCPENDLOG;
        return false;
    }

    const std::string fileNameListPath = PathJoin(bucketLogDownloadDir, LOG_FILE_NAME_LIST_FILE_NAME);
    auto fileNameListParser = std::make_unique<Module::FileNameListParser>(fileNameListPath);
    if (fileNameListParser == nullptr) {
        HCP_Log(ERR, MODULE) << "Create FileNameListParser failed" << HCPENDLOG;
        return false;
    }
    if (fileNameListParser->Open(Module::CTRL_FILE_OPEN_MODE::WRITE) != Module::CTRL_FILE_RETCODE::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Open control file failed" << HCPENDLOG;
        return false;
    }
    const std::string targetBucketName = getBucketLogConfigResponse->targetBucket;
    const std::string targetPrefix = getBucketLogConfigResponse->targetPrefix;
    HCP_Log(INFO, MODULE) << "Start download bucket logs, jobId " << m_jobId << ", bucketName " << bucketName
        << ", targetBucketName " << targetBucketName << ", targetPrefix " << targetPrefix
        << ", bucketLogDownloadDir " << bucketLogDownloadDir << HCPENDLOG;
    if (!DownloadAllBucketLog(bucketName, targetBucketName, targetPrefix, bucketLogDownloadDir, fileNameListParser)) {
        HCP_Log(ERR, MODULE) << "Download bucket log failed for bucket " << bucketName << HCPENDLOG;
        return false;
    }

    return true;
}

static bool IsValidLogName(const std::string &filePath)
{
    // 日志文件名格式：<TargetPrefix>YYYY-mm-DD-HH-MM-SS-<UniqueString>
    const std::regex pattern(R"((\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-)");
    return std::regex_search(filePath, pattern);
}

bool ObjectStorageBackupJob::SetBucketLogTimestamp(const std::string &bucketName, const std::string &logTimeStamp)
{
    INFOLOG("SetBucketLogTimestamp, bucketName: %s, logTimeStamp: %s", bucketName.c_str(), logTimeStamp.c_str());
    if (bucketName.empty() || logTimeStamp.empty()) {
        return false;
    }
    m_generalInfo.bucketLogIncBackupInfo.AddLastLogTime(bucketName, logTimeStamp);
    return true;
}

bool ObjectStorageBackupJob::DownloadAllBucketLog(
    const std::string& bucketName,
    const std::string& targetBucketName,
    const std::string& targetPrefix,
    const std::string& bucketLogDownloadDir,
    const std::unique_ptr<Module::FileNameListParser>& fileNameListParser)
{
    const std::string lastLogTime = m_backupObjectCopyInfo.bucketLogIncBackupInfo.GetLastLogTime(bucketName);
    HCP_Log(INFO, MODULE) << "lastLogTime " << lastLogTime << HCPENDLOG;
    std::string bucketLastLogName = targetPrefix + lastLogTime; // 保留前次增量副本记录的最后读取日志名称
    std::string nextMarker = bucketLastLogName;
    bool isTruncated = false;
    do {
        auto listObjectsRequest = std::make_unique<Module::ListObjectsRequest>();
        listObjectsRequest->bucketName = targetBucketName;
        listObjectsRequest->prefix = targetPrefix;
        listObjectsRequest->marker = nextMarker;
        std::unique_ptr<Module::ListObjectsResponse> listObjectsResponse;
        auto result = m_cloudClient->ListObjects(listObjectsRequest, listObjectsResponse);
        if (!result.IsSucc()) {
            HCP_Log(ERR, MODULE) << "List objects failed, bucket name " << listObjectsRequest->bucketName
                << ", prefix " << listObjectsRequest->prefix
                << ", marker " << listObjectsRequest->marker << HCPENDLOG;
            ReportJobDetailsWithLabelAndErrcode(
                std::make_tuple(JobLogLevel::TASK_LOG_ERROR, SubJobStatus::FAILED, OBSPlugin::PROGRESS0),
                "object_storage_plugin_download_bucket_log_failed_label", result.GetCommonErrorCode(),
                bucketName, targetBucketName);
            return false;
        }

        for (const auto& object : listObjectsResponse->contents) {
            // 返回的key不是日志格式文件名，需要进行过滤
            if (!IsValidLogName(object.key)) {
                HCP_Log(WARN, MODULE) << "Object is not the log file: " << object.key << HCPENDLOG;
                continue;
            }
            // 对象名中的/替换为|，避免创建多余目录
            const std::string logFileName = std::regex_replace(object.key, std::regex("/"), "|");
            const std::string logFilePath = PathJoin(bucketLogDownloadDir, logFileName);
            if (!DownloadOneBucketLog(bucketName, targetBucketName, object.key, logFilePath)) {
                HCP_Log(ERR, MODULE) << "Download bucket log falied, log file path " << logFilePath << HCPENDLOG;
                return false;
            }
            if (fileNameListParser->WriteEntry(logFileName) != Module::CTRL_FILE_RETCODE::SUCCESS) {
                HCP_Log(ERR, MODULE) << "Write object failed, log file path " << logFilePath
                    << ", targetBucketName " << targetBucketName << ", object key " << object.key << HCPENDLOG;
                return false;
            }
            bucketLastLogName = object.key;
        }

        nextMarker = listObjectsResponse->nextMarker;
        isTruncated = listObjectsResponse->isTruncated;
    } while (isTruncated);

    if (bucketLastLogName.size() >= targetPrefix.size()) {
        // 去除日志名称中的可变前缀可以得到日志时间戳
        // hcs 日志名称样例 prefix-log2024-01-24-09-01-52-14CZXWQJK93CE4IX
        const std::string logTimeStamp = bucketLastLogName.substr(targetPrefix.size());
        SetBucketLogTimestamp(bucketName, logTimeStamp);
    }

    return true;
}

bool ObjectStorageBackupJob::DownloadOneBucketLog(
    const std::string& bucketName,
    const std::string& targetBucketName,
    const std::string& key,
    const std::string& logFilePath)
{
    auto multiPartDownloadObjectRequest = std::make_unique<Module::MultiPartDownloadObjectRequest>();
    multiPartDownloadObjectRequest->bucketName = targetBucketName;
    multiPartDownloadObjectRequest->key = key;
    multiPartDownloadObjectRequest->downLoadTargetPath = logFilePath;
    std::unique_ptr<Module::MultiPartDownloadObjectResponse> multiPartDownloadObjectResponse;
    auto result = m_cloudClient->MultiPartDownloadObject(
        multiPartDownloadObjectRequest, multiPartDownloadObjectResponse);
    if (!result.IsSucc()) {
        HCP_Log(ERR, MODULE) << "Download bucket log failed,"
            << ", target bucket " << multiPartDownloadObjectRequest->bucketName
            << ", target key " << multiPartDownloadObjectRequest->key
            << ", download file name " << multiPartDownloadObjectRequest->downLoadTargetPath << HCPENDLOG;
        ReportJobDetailsWithLabelAndErrcode(
            std::make_tuple(JobLogLevel::TASK_LOG_ERROR, SubJobStatus::FAILED, OBSPlugin::PROGRESS0),
            "object_storage_plugin_download_bucket_log_failed_label", result.GetCommonErrorCode(),
            bucketName, targetBucketName);
        return false;
    }

    return true;
}

bool ObjectStorageBackupJob::ParseBucketLog(const OBSProtectSubObject& object)
{
    if (!IsBucketLogInc()) {
        HCP_Log(INFO, MODULE) << "Skip bucket log inc, jobId " << m_jobId << HCPENDLOG;
        return true;
    }

    Module::BucketLogParserParams bucketLogParserParams;
    bucketLogParserParams.storageType = m_authInfo.getStorageType();
    bucketLogParserParams.fileNameListPath = PathJoin(
        m_bucketLogDir, object.bucketName, LOG_FILE_NAME_LIST_FILE_NAME);
    bucketLogParserParams.bucketLogDir = PathJoin(m_bucketLogDir, object.bucketName);
    bucketLogParserParams.objectPrefixs = object.prefixs;
    const std::string originalDir = PathJoin(m_objectListDir, object.bucketName, OBJECT_LIST_FILE_ORIGINAL_DIR_NAME);
    bucketLogParserParams.originalObjectListDir = originalDir;
    if (!OBSPlugin::Remove(originalDir)) {
        HCP_Log(WARN, MODULE) << "Clear original object list dir failed, dir " << originalDir << HCPENDLOG;
    }
    if (!OBSPlugin::RecurseCreateDirectory(originalDir)) {
        HCP_Log(ERR, MODULE) << "Create original object list dir failed, dir " << originalDir << HCPENDLOG;
        return false;
    }
    const std::string uniqueDir = PathJoin(m_objectListDir, object.bucketName, OBJECT_LIST_FILE_UNIQUE_DIR_NAME);
    bucketLogParserParams.uniqueObjectListDir = uniqueDir;
    if (!OBSPlugin::Remove(uniqueDir)) {
        HCP_Log(WARN, MODULE) << "Clear unique object list dir failed, dir " << uniqueDir << HCPENDLOG;
    }
    if (!OBSPlugin::RecurseCreateDirectory(uniqueDir)) {
        HCP_Log(ERR, MODULE) << "Create unique object list dir failed, dir " << uniqueDir << HCPENDLOG;
        return false;
    }

    auto bucketLogParser = std::make_unique<Module::BucketLogParser>(bucketLogParserParams);
    if (bucketLogParser == nullptr) {
        HCP_Log(ERR, MODULE) << "Create BucketLogParser failed" << HCPENDLOG;
        return false;
    }
    if (!bucketLogParser->TraverseAllLogFiles()) {
        HCP_Log(ERR, MODULE) << "Traverse all log files failed" << HCPENDLOG;
        return false;
    }

    return true;
}

std::string ObjectStorageBackupJob::FindParentPrefix(const PrefixInfo& prefixInfo)
{
    std::string parentPrefix;
    if (!prefixInfo.subPrefixs.empty()) {
        for (const auto& prefix : prefixInfo.parentPrefixFilter) {
            if (prefixInfo.subPrefixs.front().find(prefix) == 0) {
                parentPrefix = prefix;
                HCP_Log(INFO, MODULE) << "Find parent prefix " << parentPrefix << HCPENDLOG;
            }
        }
    }
    return parentPrefix;
}